package com.zty.redis.util;

import com.zty.redis.exception.ShareLockException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @Author ZTY
 * @Description 实现redis分布式锁
 * 分布式锁的业务场景：
 *  任务调度：如果没有使用xxl-job，在集群系统中就会出现一个任务同一时间多个相同任务一起跑的情况
 *  算钱：操作同一数据时，数据的版本不同
 *  总结：操作统一数据，用redis的锁性能比较高
 * @Date 13:46 2023-12-13
 **/

@Component
public class RedisShareLockUtil {

    @Resource
    private RedisUtil redisUtil;

    //  超时时长
    private Long TIME_OUT = 10000L;

    /**
     * 加锁
     * @param lockKey   标识锁的唯一性
     * @param requestId 判断当前的锁是不是我加的，区别两个业务的锁是否为同一个
     * @param time  限制上锁的时间
     * @return
     */
    public boolean lock(String lockKey,String requestId,Long time){

        //  参数的校验
        //  为null或为空字符串都返回false
        if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(requestId) || time < 0){
            throw new ShareLockException("分布式锁-加锁-参数异常");
        }

        //  设置加锁状态
        Boolean result = false;

        //  记录一下是从什么时候开始的（毫秒级别）
        long currentTime = System.currentTimeMillis();

        //  计算出超时时间
        long outTime = currentTime + TIME_OUT;
        //  加锁的自旋操作（判断是否等一小会就会抢到锁）
        while (currentTime < outTime){
            //  借助redis的setnx来进行锁的设置
            result = redisUtil.setNx(lockKey, requestId, time, TimeUnit.MILLISECONDS);

            //  如果加锁成功则直接返回
            if (result){
                return result;
            }

            //  如果加锁失败，在自旋的时间范围内歇一会再重试
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //  重置当前时间
            currentTime = System.currentTimeMillis();
        }
        return result;
    }


    /**
     * 解锁
     * @param lockKey   标识锁的唯一性
     * @param requestId 判断当前的锁是不是我加的，区别两个业务的锁是否为同一个
     * @return
     */
    public boolean unLock(String lockKey,String requestId){

        //  参数的校验
        //  为null或为空字符串都返回false
        if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(requestId)){
            throw new ShareLockException("分布式锁-解锁-参数异常");
        }

        try {
            //  获取当前锁的业务Id
            String value = redisUtil.get(lockKey);

            //  两个业务一致便可解锁
            if (requestId.equals(value)){
                return redisUtil.del(lockKey);
            }
        } catch (Exception e) {

            //TODO  补日志
            e.printStackTrace();
        }

        return false;
    }


    /**
     * 尝试加锁（快速失败场景）
     * @param lockKey
     * @param requestId
     * @param time
     * @return
     */
    public boolean tryLock(String lockKey,String requestId,Long time){
        //  参数的校验
        //  为null或为空字符串都返回false
        if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(requestId) || time < 0){
            throw new ShareLockException("分布式锁-尝试加锁-参数异常");
        }

        return redisUtil.setNx(lockKey, requestId, time, TimeUnit.MILLISECONDS);
    }
}
